# -*- coding: utf-8 -*-

#This file is part of pyLot library.
#
# pyLot is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyLot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyLot.  If not, see <http://www.gnu.org/licenses/>.

__author__ = u'Pierre Puiseux, Guillaume Baty'
__copyright__ = u"Copyright 2011-2012 (C) andheo, Université de Pau et des Pays de l'Adour"
__credits__ = [u'Pierre Puiseux', u'Guillaume Baty']
__license__ = "GNU Lesser General Public License"

__all__ = ['Application', 'PluginManager',
           'APPLICATION', 'find_app_plugins', 'find_plugins',
           'display_application_infos']

import os
import codecs
import re
import sys
import warnings

from envisage.api import Application as EnvisageApplication
from envisage.api import Plugin

from pyLot.minimal import (InheritableSingleton, UserConfigDir, ENCODING, Path,
                           LOGGER, log_exception, get_data,
                           DEFAULT_PROCESS_HANDLER)

from pyLot.recording import Recorder, set_recorder

from .core_plugin import CorePlugin

interfaces = [Plugin]

DEBUG_PLUGINS = True

def find_plugins(plugin_dict, pattern=r'.*\.py'):
  plugin_dirs = plugin_dict.keys()
  regexpr = re.compile(pattern)
  plugins = []
  modpaths = []
  for module_dir in plugin_dirs :
    if not module_dir.isdir() :
      continue
    elif plugin_dict[module_dir] :
      for class_path in plugin_dict[module_dir] :

        module_name = class_path.split(u'.')[0:-1]
        obj_name = class_path.split(u'.')[-1]

        module_name.insert(0, module_dir.name)
        modpath = u'.'.join(module_name)

        try :
          mod = __import__(modpath, fromlist=[modpath])
        except ImportError, error:
          LOGGER.info(log_exception(error))
          continue

        try :
          obj = getattr(mod, obj_name)
        except AttributeError, error:
          LOGGER.info(log_exception(error))
          continue
        else :
          if issubclass(obj, Plugin) and obj not in interfaces :
            plugins.append(obj)
    else :
      for pyfile in module_dir.walkfiles_re(regexpr) :
        modpath = module_dir.parent.relpathto(pyfile)
        for ext in (u'.pyc', u'.pyo', u'.pyd', u'.py'):
          if modpath.endswith(ext) :
            modpath = modpath.replaceext(u'', ext)
            break
        modpath = '.'.join(modpath.splitall()[1:])
        modpath = modpath.replace(os.sep, u'.')
        if modpath not in modpaths :
          modpaths.append(modpath)
          if DEBUG_PLUGINS :
            mod = __import__(modpath, fromlist=[modpath])
          else :
            try :
              mod = __import__(modpath, fromlist=[modpath])
            except ImportError, error :
              LOGGER.info(log_exception(error))
          for obj_name in dir(mod):
            obj = getattr(mod, obj_name)
            try :
              if issubclass(obj, Plugin) and obj not in interfaces :
                plugins.append(obj)
            except TypeError :
              pass
        else :
          continue
  return plugins

def find_app_plugins(confdir, pattern=r'.*plugindef\.py',
                     default_list=[]):

  plugin_lst_path = Path(confdir, u'plugin_list.txt')
  if not plugin_lst_path.exists() :
    plugin_lst = codecs.open(plugin_lst_path, u'w', u'utf-8')
    for lst in default_list :
      plugin_lst.write(u'%s\n' % lst)
    plugin_lst.close()

  plugin_dict = {}
  plugin_lst = codecs.open(plugin_lst_path, u'r', u'utf-8')
  pack = None
  for line in plugin_lst :
    line = line.strip()
    if line.startswith(u'#') or not line :
      continue

    if line.startswith(u'-') :
      plugin_class_path = line[1:].lstrip()
      if pack is None :
        LOGGER.info('Missing pack name for plugin "%s"' % plugin_class_path)
      else :
        plugin_dict[pack].append(plugin_class_path)
    elif line :
      plugin_path = Path(line)
      if plugin_path.isabs() :
        pack = plugin_path
      else :
        try :
          plugin_path = get_data(plugin_path, u'.')
        except ImportError :
          LOGGER.info('plugin module name "%s" is not valid' % plugin_path)
          continue
        else :
          pack = plugin_path

      if pack :
        if pack not in plugin_dict :
          plugin_dict[pack] = []

  return find_plugins(plugin_dict, pattern)

def display_plugin_infos(plugin):
#  print '-' * 80
  if isinstance(plugin, Plugin) :
    print '  - Plugin.id: %s' % plugin.id
#    print '  - Nom: %s' % plugin.name
  else :
    print '  - Objet.id: %s' % plugin.id
#  print
#  print '  repr: %r' % plugin
#  print '  type: %s' % type(plugin)
#  print '  home: %s' % plugin.home
#  print
#  print "  Points d'extension :"
#  ext_pts = plugin.get_extension_points()
#  ext_pts.sort()
#  for ext_pt in ext_pts :
#    print '    *** %s' % ext_pt.id


def display_application_infos(application):
  display_plugin_infos(application)
#  ext_lst = []
  for plugin in application :
    display_plugin_infos(plugin)
#    ext_lst += plugin.get_extension_points()

#  print
#  print
#  for ext_pt in ext_lst :
#    print '-' * 80
#    print 'Point d\'extension : %r' % ext_pt.id
#    for plugin in application :
#      exts = plugin.get_extensions(ext_pt.id)
#      if exts :
#        print '  - %s fournit %d extensions de type %s' % (plugin.id, len(exts), type(exts[0]))
#    print

class PluginManager(EnvisageApplication):

  def _home_default(self):
    """ Trait initializer. """
    home = EnvisageApplication._home_default(self)
    return UserConfigDir(home.decode(ENCODING))


class Application(InheritableSingleton):

  _identifier = None
  _plugin_manager = None
  _process_handler = None
  _default_list = []

  FORCE_UI_GENERATION = os.environ.get('FORCE_UI_GENERATION', False)

  def initProcessHandler(self):
    if self._process_handler is None :
      self.setProcessHandler(DEFAULT_PROCESS_HANDLER)

  def setProcessHandler(self, process_handler):
    self._process_handler = process_handler

  def getProcessHandler(self):
    if self._process_handler is None :
#      print "Creating default process handler"
      self.initProcessHandler()
    return self._process_handler

  def setPluginManager(self, plugin_manager, default_list=[]):
    self._default_list = default_list
    self._plugin_manager = plugin_manager
    confdir = self.home
    if not confdir.check() :
      sys.exit(1)

  def initPluginManager(self, default_list=[]):
    if self._plugin_manager is None :
      if self.identifier is None :
        identifier = 'pyLot'
      else :
        identifier = self.identifier
      pm = PluginManager(id=identifier)
      self.setPluginManager(pm, default_list=default_list)

  def getPluginManager(self):
    if self._plugin_manager is None :
#      print "Creating default plugin manager"
      self.initPluginManager()
    return self._plugin_manager

  def getIdentifier(self):
    if self._identifier is None :
      warnings.warn(u'APPLICATION.identifier not defined, applications using extensions may fail')
    return self._identifier

  def setIdentifier(self, identifier):
#    print 'IDENTIFIER=', identifier
    self._identifier = identifier
    # Create confdir if necessary

  def initIdentifier(self, identifier):
    if self._identifier is None :
      self.setIdentifier(identifier)

  home = userconf = property(fget=lambda self:self.getPluginManager().home)
  identifier = property(fget=getIdentifier, fset=setIdentifier)

  def setRecorder(self, recorder):
    self.recorder = recorder

  def getRecorder(self):
    try :
      self.recorder
    except AttributeError :
#      print "Creating default script recorder"
      self.recorder = Recorder()
    return self.recorder


  def initRecorder(self):
    set_recorder(self.getRecorder())

  def startRecorder(self):
    self.recorder.clear()
    self.recorder.recording = True
    self.record(u'from pyLot.core import *')

  def stopRecorder(self):
    self.recorder.recording = False
    print '\n\nScript enregistré:\n', self.recorder.get_code()

  def record(self, pycode):
    self.recorder.record(pycode)

  def addPlugin(self, plugin):
    pm = self.getPluginManager()
    pm.add_plugin(plugin)

  def loadUserPlugins(self):
    plugins = find_app_plugins(confdir=self.home,
                               default_list=self._default_list)

    for plugin_class in plugins :
      plugin = plugin_class()
      for core_plugin_class in plugin.get_extensions('pyLot.plugins.coreplugins'):
        core_plugin = core_plugin_class()
        self.addPlugin(core_plugin)
      self.addPlugin(plugin)

  def init(self, identifier, plugin_list, core_plugin=CorePlugin):
    self.initIdentifier(identifier)
    self.initPluginManager(plugin_list)
    self.addPlugin(core_plugin())
    self.loadUserPlugins()
    self.start()

  def start(self):
    pm = self.getPluginManager()
    pm.start()

  def stop(self):
    pm = self.getPluginManager()
    pm.stop()

  io = property(fget=lambda self:self.process.io)
  process = property(fget=getProcessHandler,
                     fset=setProcessHandler)

APPLICATION = Application()


if __name__ == '__main__' :

  plugin_dirs = [
    Path(u'/home/guillaume/prog/PyAero/applications'),
    Path(u'.').abspath()
    ]

  plugins = find_plugins(plugin_dirs=plugin_dirs)
  for plugin in plugins :
    print plugin()
